//
// async_udp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
// ref: https://www.boost.org/doc/libs/1_78_0/doc/html/boost_asio/example/cpp11/echo/async_udp_echo_server.cpp
//

#include <boost/asio.hpp>
#include <boost/timer.hpp>
#include <boost/version.hpp>
#include <cstdlib>
#include <iostream>

using namespace boost;
using boost::asio::ip::udp;

// #define debug std::cout

#define debug  \
    if (false) \
    std::cout

const int MAX_PACKET_SIZE = 1024;

class EmuFpga {

    enum { max_length = MAX_PACKET_SIZE };
    char data_[max_length];
    udp::endpoint sender_endpoint_;
    boost::asio::io_context io_context;
    udp::socket sock;

    int recv_count = 0;
    int send_count = 0;

public:
    EmuFpga(int port)
        : sock(io_context, udp::endpoint(udp::v4(), port))
    {
        // boost::asio::socket_base::receive_buffer_size recv_option(8*65534);

        sock.set_option(boost::asio::socket_base::receive_buffer_size(1024 * 1024 * 100));
        sock.set_option(udp::socket::reuse_address(true));
    }

    void do_recevie()
    {
        sock.async_receive_from(
            boost::asio::buffer(data_, max_length), sender_endpoint_,
            [this](boost::system::error_code ec, std::size_t bytes_recvd) {
                recv_count++;

                if (recv_count % 10000 == 0)
                    std::cout << "recive from: "
                              << sender_endpoint_.address() << ":"
                              << sender_endpoint_.port() << " "
                              /* << sender_endpoint_.protocol()  */
                              << "recv: " << recv_count << " "
                              << "send: " << send_count << " "
                              << "recv-bytes: " << bytes_recvd << " "
                              << "\n";

                if (!ec && bytes_recvd > 0) {
                    do_send(bytes_recvd);
                } else {
                    do_recevie();
                }
            });
    }

    void do_send(std::size_t length)
    {
        debug << "do send....\n";
        sock.async_send_to(
            boost::asio::buffer(data_, 256), sender_endpoint_,
            [this](boost::system::error_code /*ec*/, std::size_t /*bytes_sent*/) {
                send_count++;
                do_recevie();
            });
    }

    void start()
    {
        do_recevie();
        io_context.run();
    }
};

class PcSender {
    // 这个才是更像pc端

    boost::asio::io_context io_context;
    udp::endpoint send_ep; // to fpga
    // udp::endpoint recv_ep(udp::v4(), 0); // from local pc, 发送端口，port为0时，系统随机分配
    udp::endpoint recv_ep; // from local pc
    udp::socket sock; // recive

public:
    PcSender(std::string remote_ip, int rempote_port, int local_port)
        : send_ep(boost::asio::ip::address::from_string(remote_ip), rempote_port)
        , recv_ep(udp::v4(), rempote_port + 1)
        // , socket(io_context, recv_ep)
        , sock(io_context, udp::endpoint(udp::v4(), rempote_port + 1))
    {
        local_port = rempote_port + 1;
        sock.set_option(boost::asio::socket_base::receive_buffer_size(1024 * 1024 * 100));
        sock.set_option(udp::socket::reuse_address(true));
    }

    int max_send = 4000000;
    int real_send = 0;
    int real_recv = 0;
    void start_hpc_send()
    {
        do_recevie();
        do_send();

        // for (int i = 0; i < 1000000; i++) {
        //     // do_send();
        //     std::cout << "after run...............................\n";
        //     // sleep(1);
        // }
        io_context.run();
        debug << "send packet: " << real_send << "\n";
        debug << "recv packet: " << real_recv << "\n";
    }
    void do_send()
    {
        if (real_send > max_send)
            return;
        // for (int i = 0; i < 1000; i++)
        //     ;
        // usleep(1);

        debug << "recive socket info: "
              << recv_ep.address() << ":"
              << recv_ep.port() << " "
              /* << send_ep2.protocol() */
              << "\n";
        char buf[MAX_PACKET_SIZE];
        sock.async_send_to(boost::asio::buffer(buf, MAX_PACKET_SIZE), send_ep,
            [this](boost::system::error_code /*ec*/, std::size_t /*bytes_sent*/) {
                real_send++;
                do_send();
                // start_hpc_send();
            });
    }

    udp::endpoint _ignore_ep;
    char buf[MAX_PACKET_SIZE];
    int max_length = MAX_PACKET_SIZE;
    boost::timer time;
    long total_recv_bytes = 0;

    void do_recevie()
    {
        // std::cout << "do_recevie...: recv: \n";
        // udp::endpoint _ignore_ep; // recv peer ep, 这是收到包之后的第二次发的ep, 这里因为提前约定，后面实际不用这个ep
        sock.async_receive_from(boost::asio::buffer(buf, max_length), _ignore_ep,
            [this](boost::system::error_code ec, std::size_t bytes_recvd) {
                real_recv++;
                // total_recv_bytes += bytes_recvd;
                total_recv_bytes += MAX_PACKET_SIZE;
                if (ec) {
                    std::cerr << "recv error: " << ec << "\n";
                    do_recevie();
                    return;
                }
                if (real_recv % 1000 == 0)
                    std::cout << "recive success from: "
                              << _ignore_ep.address() << ":"
                              << _ignore_ep.port() << " <--> "
                              << send_ep.address() << ":"
                              << send_ep.port() << " "
                              << "recv: " << real_recv << " send: " << real_send << " "
                              << "rate: " << float(real_recv) / real_send * 100 << "% "
                              << "speed: " << total_recv_bytes / 1024.0 / 1024 / time.elapsed() << "MB/s "
                              << "recieive_bytes: " << bytes_recvd << " "
                              << "time: " << time.elapsed() << " "
                              << "\n";

                // if (!ec && bytes_recvd > 0) {
                //     // std::string recv_msg(buf, bytes_recvd);
                //     std::cout << "recive from: "

                //               /* << send_ep2.protocol() */
                //               << "\n";
                //     std::cout << "server echoed us: msg: >>> " << recv_msg << "\n";
                // }
                // start_hpc_send();
                // do_send();
                do_recevie();
            });
    }
    void start()
    {
        start_hpc_send();
        // do_recevie();
        // do_send();
        // io_context.run();
    }
};

int main(int argc, char* argv[])
{
    std::cout << "use boost version: " << BOOST_LIB_VERSION << std::endl;
    try {
        if (argc != 4) {
            std::cerr << "Usage: test [fpga|pc] ip port\n";
            return 1;
        }
        std::string command(argv[1]);
        if (command == "fpga") {
            EmuFpga(std::atoi(argv[3])).start();
        } else if (command == "pc") {
            PcSender(argv[2], std::atoi(argv[3]), 0).start();
        } else {
            std::cerr << "Error: command error [" << argv[1] << "]\n";
            return 1;
        }

    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
    return 0;
}